<?php
/**
 * bos 清理事件监听
 *
 * Author: Zeon
 * Date: 2018/1/17
 * Created by Panxsoft.
 */
namespace App\Observers;

use App\Observers\Contract\ObserverCleanable;
use App\Services\Utils\OSSable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;

class OSSCleanObserver
{
    use OSSable;

    const COLUMNS_PROPERTY = 'oss_columns';

    /**
     * @param Model $model
     */
    public function updated(Model $model)
    {
        $this->mapDeleteKeys($this->getChangedOriginalKeys($model));
    }

    /**
     * @param Model $model
     */
    public function deleted(Model $model)
    {
        $this->mapDeleteKeys($this->getOriginalKeys($model, $this->getKeyColumns($model)));
    }

    /**
     * @param Model $model
     * @param array $bos_columns
     * @return array
     */
    private function getOriginalKeys(Model $model, array $bos_columns)
    {
        return array_map([$model, 'getOriginal'], $bos_columns);
    }

    /**
     * @param array $keys
     * @return array
     */
    private function mapDeleteKeys(array $keys)
    {
        $normalize_keys = array_map([$this, 'httpUrlToKey'], $keys);

        return array_map([$this, 'deleteKey'], $normalize_keys);
    }

    /**
     * @example
     *  http://hathor.oss-cn-shenzhen.aliyuncs.com/user/avatar/c2019_05_29.png => user/avatar/c2019_05_29.png
     *
     * @param $key
     * @return mixed
     */
    public function httpUrlToKey($key)
    {
        $bucket = config('pkx.ali.oss.bucket');
        $endpoint = config('pkx.ali.oss.endpoint');
        $has_config = $bucket && $endpoint;
        $bucket_url = "{$bucket}.{$endpoint}/";

        if (
            ! $has_config // 配置丢失
            || mb_strpos($key, 'http') !== 0 // 开头不是 http
            || mb_strpos($key, $bucket_url) === false // key 中没有 bucket url 地址
        ) {
            return $key; // 直接返回原 key
        }

        return last(explode($bucket_url, $key));
    }

    /**
     * 返回更新后已经改变的 keys 的原始（需要被清理删除的）值
     *
     * @param Model $model
     * @return array
     */
    private function getChangedOriginalKeys(Model $model)
    {
        /**
         * @see Model::originalIsEquivalent
         * 因为上面的方法使用了 castAttribute 对比属性变动，所以可以将
         * key 更新成对应的网址形式， 文件不会被删除
         */
        $dirty_columns = array_keys($this->getModelDirty($model));

        $key_columns = $this->getKeyColumns($model);

        $dirty_key_columns = array_values(array_intersect($key_columns, $dirty_columns));

        $original_keys = $this->getOriginalKeys($model, $dirty_key_columns);

        return $original_keys;
    }

    /**
     * @param ObserverCleanable|Model $model
     *
     * @return array|mixed
     */
    private function getKeyColumns(ObserverCleanable $model)
    {
        // if model need clean keys, should implements ObserverCleanable
        return $model->keyCleanColumns();
    }

    private function getModelDirty(Model $model)
    {
        $dirty = [];

        foreach ($model->getAttributes() as $key => $value) {
            // 新旧数据一致
            $is_origin_equal = $model->originalIsEquivalent($key, $value);
            // 数据从 key 变成相同的 url
            $is_key_to_url = $this->isKeyToIdentityUrl($model, $key);

            if (! ($is_origin_equal || $is_key_to_url)) {
                $dirty[$key] = $value;
            }
        }

        return $dirty;
    }

    /**
     * 用来处理原来数据库保存的是 key， 前端 或 App 修改保存时提交了网址，导致更新时原数据被删除的情况
     *
     * @param Model $model
     * @param $key
     *
     * @return bool
     */
    private function isKeyToIdentityUrl(Model $model, $key): bool
    {
        $new_key = $model->getAttribute($key);
        $origin_key = $model->getOriginal($key);

        return Str::startsWith($new_key, 'http')
            && Str::endsWith($new_key, $origin_key);
    }
}
